package com.ff.xyh.consumer.online.user.controller;

import com.alibaba.fastjson.JSON;
import com.ff.xyh.common.servicebase.config.MyApplicationContextAware;
import com.ff.xyh.common.utils.CommonUtil;
import com.ff.xyh.common.vo.message.ActionTypeAndRoomMessage;
import com.ff.xyh.common.vo.message.RoomMessageVo;
import com.ff.xyh.consumer.online.user.config.MyLock;
import com.ff.xyh.consumer.online.user.config.UserRabbitConfig;
import com.ff.xyh.consumer.online.user.config.Users;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 目前开发以8011为主
 * 设计概要：
 *      一、本类是一个连接new一个对象，所以无法通过autowired获取类 所以使用懒汉式单例模式
 *          将 redisTemplate 和 rabbitTemplate 从对象池中载入到类静态属性
 *      二、用户存入redis的数据是 消息接收队列的路由值， 所以用户加入过的群聊的键 需要用
 *          消息接收队列的路由值，也就是 userId 对应 消息接收队列的路由值，
 *          userId+消息接收队列的路由值 对应 用户进入的群聊id set集合
 */

@ServerEndpoint(value = "/connect")
@Component
public class OnlineUserController {

    private Session session;
    private String userId;

    private static RedisTemplate redisTemplate;
    private static RabbitTemplate rabbitTemplate;

    public Session getSession() {
        return session;
    }
    public String getUserId() {
        return userId;
    }

    /** 手动注入 redis 和 rabbit 的模板对象 */
    public OnlineUserController() {
        if (redisTemplate == null) {
            System.out.println("redisTemplate 创建");
            if (MyApplicationContextAware.getApplicationContext() != null) {
                redisTemplate = MyApplicationContextAware.getBean("redisTemplate", RedisTemplate.class);
            }
        }
        if (rabbitTemplate == null) {
            System.out.println("rabbitTemplate 创建");
            if (MyApplicationContextAware.getApplicationContext() != null) {
                rabbitTemplate = MyApplicationContextAware.getBean("rabbitTemplate", RabbitTemplate.class);
            }
        }
    }

    /**
     * 一个用户连接时生成一个controller 一个controller也会包含一个session
     * 用户登录后保持连接 可以做在线业务
     * 将 <userId, routingKey> 存入redis，读取数据库，获取用户需要监听消息的所有群聊id，
     * 用 userId 接上服务器 routingKey 作为键， 将获取到的所有群聊id作为值，存入到redis的set集合里，
     * 并获取 redis 中以上面每个 roomId 作为键的set集合，如果集合存在，则将 userId 添加到集合，并遍历集合，
     * 将用户上线的消息发送给集合中每一个用户，如果集合不存在，则新建以该 roomId 为键的set集合，并将当前userId存入集合。
     */
    @OnOpen
    public void onOpen(Session session) {
//        ws:/localhost:8011/connect?userId=12224
        Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
        String userId = requestParameterMap.get("userId").get(0);
        this.session = session;
        this.userId = userId;

        System.out.println("userId:" + userId);
        // 当服务器存在该用户时，先断开原用户连接，再将 userId 和 当前服务器的 用户 routingKey 绑定到redis
        if (redisTemplate.hasKey("online" + userId)) {
            System.out.println("用户已被登录");
            new MyLock(userId);
            String userMessageRoutingKey = (String) redisTemplate.opsForValue().get("online" + userId);
            // userRoutingKey 不存在或为空值 时 反馈给用户登录失败并断开连接，并尝试删除空值键
            if (StringUtils.isEmpty(userMessageRoutingKey)) {
                // 发送登录失败消息
                ActionTypeAndRoomMessage message = ActionTypeAndRoomMessage.type(19)
                        .data("message", new RoomMessageVo()
                                .setUserId(userId)
                                .setMessage("未知错误，登录失败，请重新尝试登录，如果依旧失败，请联系管理员。"));
                Future<Void> sendFuture = session.getAsyncRemote().sendObject(message);
                if (sendFuture.isDone() || sendFuture.isCancelled()) {
                    try {
                        session.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                // 删除空值键
                redisTemplate.delete("online" + userId);
                return ;
            } else { // 不是空值键 需要将已存在的用户下线 然后等待5s内确认
                rabbitTemplate.convertAndSend(
                        UserRabbitConfig.EXCHANGE_DIRECT_USER,
                        userMessageRoutingKey,
                        ActionTypeAndRoomMessage.type(16)
                                .data("message", new RoomMessageVo()
                                        .setId(UserRabbitConfig.ROUTING_KEY_USER_LOCK) // 解锁进程作用
                                        .setToUserId(userId)
                                        .setMessage("账号被登录。如不是您授权的操作，请修改密码或联系客服冻结"))
                );
                // 最多等待五秒后让队列消费消息后解锁等待
                try {
                    MyLock.getLocks().get(userId).waitForConfirm(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 移除锁， <userId, routingKey> 存入redis 向用户队列发送消息 让队列消费者完成后续操作
            MyLock.getLocks().remove(userId);

        } else { // 用户没有被登录
            System.out.println("用户没有被登录");
        }

        // 绑定用户到特定消息队列
        redisTemplate.opsForValue().set("online" + this.userId,
                UserRabbitConfig.ROUTING_KEY_MESSAGE);

        Map<String, OnlineUserController> users = Users.getUsers();
        synchronized (users) {
            users.put(this.userId, this);
        }
        // 发送登录成功消息到队列进行消费
        rabbitTemplate.convertAndSend(
                UserRabbitConfig.EXCHANGE_DIRECT_USER,
                UserRabbitConfig.ROUTING_KEY_MESSAGE,
//                UserRabbitConfig.ROUTING_KEY_USER,
                ActionTypeAndRoomMessage.type(18)
                        .data("message", new RoomMessageVo()
//                                .setId("这是消息id，只有在发送群聊消息时才会有")
//                                .setRoomId("这是群聊id")
//                                .setUserNickname("发消息的用户的昵称")
//                                .setUserAvatar("发消息的用户的头像链接，非完整链接")
//                                .setToUserId(this.userId)
//                                .setGmtCreated(new Date())
//                                .setUserNumber(1)
                                .setUserId(this.userId)
                                .setMessage("登录成功"))
        );
        // 以上已将 <online+userId， routingKey> 存入redis
        // 发送登录成功消息给消息接收队列 让消息接收队列监听器处理其他事务
    }

    /**
     * 用户下线时
     * 从 redis 获取以 userId+routingKey 为键的 set集合，遍历集合中的roomId，将 redis 中以该 roomId 为键的集合中的
     * 当前的 userId 删除后遍历集合，将该用户下线的消息发送给群聊中的其他用户。还需要删除 userId 为键的String
     */
    @OnClose
    public void onClose() {
        // 除去服务器和redis中保存的用户信息
        Map<String, OnlineUserController> users = Users.getUsers();
        synchronized (users) {
            users.remove(this.userId);
        }
        String userId = this.userId;
        String userIdAndMessageRoutingKey =
                this.userId + UserRabbitConfig.ROUTING_KEY_MESSAGE;
//            1)、  添加Set缓存(值可以是一个，也可是多个)(2/3是1的递进值）
//            1、通过redisTemplate设置值
//            redisTemplate.boundSetOps("setKey").add("setValue1", "setValue2", "setValue3");
//            2、通过BoundSetOperations设置值
//            BoundSetOperations setKey = redisTemplate.boundSetOps("setKey");
//            setKey.add("setValue1", "setValue2", "setValue3");
//            3、通过ValueOperations设置值
//            SetOperations setOps = redisTemplate.opsForSet();
//            setOps.add("setKey", "SetValue1", "setValue2", "setValue3");

//            2)、设置过期时间(单独设置)
//            redisTemplate.boundValueOps("setKey").expire(1,TimeUnit.MINUTES);
//            redisTemplate.expire("setKey",1,TimeUnit.MINUTES);

//            3)、根据key获取Set中的所有值
//            //1、通过redisTemplate获取值
//            Set set1 = redisTemplate.boundSetOps("setKey").members();
//            //2、通过BoundSetOperations获取值
//            BoundSetOperations setKey = redisTemplate.boundSetOps("setKey");
//            Set set2 = setKey.members();
//            //3、通过ValueOperations获取值
//            SetOperations setOps = redisTemplate.opsForSet();
//            Set set3 = setOps.members("setKey");

//            4)、根据value从一个set中查询,是否存在
//            Boolean isEmpty = redisTemplate.boundSetOps("setKey").isMember("setValue2");
//            5)、获取Set缓存的长度
//            Long size = redisTemplate.boundSetOps("setKey").size();
//            6)、移除指定的元素
//            Long result1 = redisTemplate.boundSetOps("setKey").remove("setValue1");
//            7)、移除指定的key
//            Boolean result2 = redisTemplate.delete("setKey");

//            若用户有进入过房间 则存在该键
//            获取 <userIdAndRoutingKey, roomIdSet>
        if (redisTemplate.hasKey(userIdAndMessageRoutingKey)) {
            BoundSetOperations  userIdAndRoutingKeySetOps = redisTemplate.boundSetOps(userIdAndMessageRoutingKey);
//            得到用户在线的群聊
            Set<String> roomIdSet = userIdAndRoutingKeySetOps.members();
            for (String roomId : roomIdSet) {
                if(! redisTemplate.hasKey(roomId))  // 不存在roomId这个键 则跳过
                    continue;
//                获取 <roomId, userIdSet>
                BoundSetOperations roomIdSetOps = redisTemplate.boundSetOps(roomId);
//                将用户从群聊列表移除
                roomIdSetOps.remove(userId);
            }
        }
//                发送下线消息发送到消息处理队列进行消费
        rabbitTemplate.convertAndSend(
                UserRabbitConfig.EXCHANGE_DIRECT_USER,
                UserRabbitConfig.ROUTING_KEY_MESSAGE,
                ActionTypeAndRoomMessage.type(17)
                        .data("message", new RoomMessageVo()
                                .setUserId(userId))
        );


        // 将userId， userIdAndRoutingKey 从 redis 移除
        // 路由用
        if (redisTemplate.hasKey("online" + userId)) {
            if (! redisTemplate.delete("online" + userId)) {
                // 路由键删除失败则设置为空并且20毫秒后过期
                redisTemplate.opsForValue().set("online" + userId, "",
                        20, TimeUnit.MILLISECONDS);
            }
        }
        // 记录进入的房间用 到消息队列里删
//        if (redisTemplate.hasKey(userIdAndMessageRoutingKey)) {
//            redisTemplate.delete(userIdAndMessageRoutingKey);
//        }


////        redis中有存在的房间信息
//        if (redisTemplate.hasKey(this.roomId)) {
//            // 获取房间所在服务器的路由值
//            String roomRoutingKey = (String) redisTemplate.opsForValue().get(this.roomId);
//            RoomMessageVo message = new RoomMessageVo();
//            message.setRoomId(this.roomId).setUserId(this.userId);
//            rabbitTemplate.convertAndSend(
//                    RoomRabbitConfig.EXCHANGE_DIRECT_CHAT,
//                    roomRoutingKey,
//                    ActionTypeAndRoomMessage.type(8)
//                            .data("message", message)
//            );
//        } else { // 没有存在的房间信息 尝试性删除当前服务器对应的房间信息
//            Rooms.getRooms().remove(this.roomId);
//        }

    }

    /**
     * 用户发消息时
     * 从 redis 获取以消息体中的 roomId 为键的 set集合，遍历集合中的 userId，当 userId 不是 当前的userId时，
     * 找到redis中以 userId 为键的routingKey，将消息发送到对应的队列中处理。当 userId 为 当前userId时，将消
     * 息类型改为成功发送后发送给用户。
     */
    // 客户端向服务端
    @OnMessage
//    public void onMessage(String msg) {
    public void onMessage(String msg) {
//        /** 如果是将用户踢出群 则消息体 userId 对应 operatorId toUserId存入bannerId */
        // {"actionType":5,"messageBody":{"message":{"roomId":"12312345","message":"这是消息","userId":"65432211"}}
////        Gson gson = new Gson();
////        MessageOwnerAndRoomMessage message = gson.fromJson(msg, MessageOwnerAndRoomMessage.class);
//        // type-roomId-messageText
////          9-1233-这是消息1

//        System.out.println(msg);
//        String[] split = msg.split("-");
////        MessageOwnerAndRoomMessage message = JSON.parseObject(msg, MessageOwnerAndRoomMessage.class);
//        String messageType = split[0];
//        String roomId = split[1];
//        String message = split[2];
////        获取用户routingKey，将消息发送到队列进行消费
//        rabbitTemplate.convertAndSend(
//                UserRabbitConfig.EXCHANGE_DIRECT_USER,
//                UserRabbitConfig.ROUTING_KEY_MESSAGE,
//                ActionTypeAndRoomMessage.type(Integer.parseInt(messageType))
//                        .data("message", new RoomMessageVo()
//                                .setMessage(message)
//                                .setRoomId(roomId)
//                                .setUserId(this.userId))
//        );
        System.out.println("msg ==> " + msg);
        ActionTypeAndRoomMessage message = JSON.parseObject(msg, ActionTypeAndRoomMessage.class);
        System.out.println("message ==> " + message);
        RoomMessageVo messageVo = CommonUtil.getMessageInMap(message.getMessageBody(), RoomMessageVo.class);
        if (messageVo == null) {
            session.getAsyncRemote().sendText(JSON.toJSONString(ActionTypeAndRoomMessage.type(0)
                    .data("message", new RoomMessageVo().setMessage("没有参数"))));
            return ;
        }
        messageVo.setUserId(this.userId);

        rabbitTemplate.convertAndSend(
                UserRabbitConfig.EXCHANGE_DIRECT_USER,
                UserRabbitConfig.ROUTING_KEY_MESSAGE,
                ActionTypeAndRoomMessage.type(message.getActionType())
                        .data("message", messageVo)
        );

//        MessageOwnerAndRoomMessage message = JSON.parseObject(msg, MessageOwnerAndRoomMessage.class);
//        ActionTypeAndRoomMessage message = new ActionTypeAndRoomMessage();
//        message.setActionType(Integer.parseInt(split[0]))
//                .data("message", new RoomMessageVo().setMessage(split[1]));
//                // message中的messageBody应包含有一个RoomMessage对象
//        System.out.println("服务器端接收到的消息体： " + message);
//        RoomMessageVo roomMessageVo = message.getMessageBody().get("message");
//        if (message.getActionType() == 12)
//            roomMessageVo.setToUserId(split[2]);
//        roomMessageVo.setRoomId(this.roomId).setUserId(this.userId);
//        // 获取房间所在服务器的路由值
//        String roomRoutingKey = (String) redisTemplate.opsForValue().get(this.roomId);
//        rabbitTemplate.convertAndSend(
//                RoomRabbitConfig.EXCHANGE_DIRECT_CHAT,
//                roomRoutingKey,
//                message
//        );

//        rabbitTemplate.convertAndSend(
//                UserRabbitConfig.EXCHANGE_DIRECT_USER,
//                userRoutingKey,
//                ActionTypeAndRoomMessage.type(17)
//                        .data("message", new RoomMessageVo()
//                                .setUserId(userId)
//                                .setToUserId(userIdInRoom))
//        );

    }


    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
        ActionTypeAndRoomMessage message = ActionTypeAndRoomMessage.type(0)
                .data("message", new RoomMessageVo()
                        .setMessage("服务器出错了")
                        .setUserId(this.userId));
        session.getAsyncRemote().sendObject(message);
    }

}